// ----------------------------------------------------------------------------
// Copyright (C) 2003 Rafael H. Bordini, Jomi F. Hubner, et al.
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// 
// To contact the authors:
// http://www.inf.ufrgs.br/~bordini
// http://www.das.ufsc.br/~jomi
//
//----------------------------------------------------------------------------

package jason.asSyntax;

import jason.JasonException;
import jason.asSemantics.Agent;
import jason.asSemantics.Message;
import jason.asSemantics.Agent.Module;
import jason.asSyntax.parser.ParseException;
import jason.asSyntax.parser.Token;
import static jason.asSyntax.ASSyntax.*;

import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Represents an atom (a positive literal with no argument and no annotation, e.g. "tell", "a").
 */
public class Atom extends Literal {
	
	private static final long serialVersionUID = 1L;
	private static Logger logger = Logger.getLogger(Atom.class.getName());
	
	String functor; // let's make it mutable! -> modularity
	
	private boolean modulePrefixApplied = false;
	private String moduleUsed = null;
	private String prettyModulePrefix = null;
	private Module checkForExportModule = null;
	private String functorWithoutModule = null;
	
	public Atom(String functor) {
		if (functor == null) logger.log(Level.WARNING, "A structure functor should not be null!", new Exception());
		this.functor = functor;
	}
	
	public Atom(String functor, Boolean mutable) {
		this(functor);
		this.mutable = mutable;
	}
	
	public Atom(Literal l) {
		this.functor = l.getFunctor();
		
		predicateIndicatorCache = l.predicateIndicatorCache;
		hashCodeCache = l.hashCodeCache;
		srcInfo = l.srcInfo;
		
		if (l instanceof Atom) {
			Atom a = (Atom)l;
			modulePrefixApplied = a.modulePrefixApplied;
			moduleUsed = a.moduleUsed;
			prettyModulePrefix = a.prettyModulePrefix;
			checkForExportModule = a.checkForExportModule;
			functorWithoutModule = a.functorWithoutModule;
		}
	}
	
	public String getFunctor() {
		return functor;
	}
	
	public final String getFunctorWithoutModule() {
		return functorWithoutModule;
	}
	
	public void applyModulePrefix(String moduleName, String modulePrefix, boolean isNormal) { // used to apply the module prefixes
		if (isNormal && isMutable() && !modulePrefixApplied) {
			functorWithoutModule = functor;
			modulePrefixApplied = true;
			functor = modulePrefix + functor;
		}
		
		if (moduleUsed == null) {
			if(isNormal) prettyModulePrefix = moduleName;
		} else {
			prettyModulePrefix = moduleName + Agent.Module.OPERATOR + moduleUsed;
		}
	}
	
	protected void useModule(Agent agent, String moduleName, SourceInfo srcInfo, Token token) throws ParseException {
		setSrcInfo(srcInfo);
		if (agent == null || !agent.isDoImports()) return;
		Agent.Module module = agent.getModule(moduleName, '[' + getErrorMsg() + ']', token);
		if (module == null) {
			throw new ParseException('[' + getErrorMsg() + "] There is no module imported with name '" + moduleName + "'.", token);
		}
		if (!moduleName.isEmpty() && (!isMutable() || RESERVED_TERMS.containsKey(functor) || RESERVED_ANNOTS.containsKey(functor) || RESERVED_FUNCTION_PARAMS.containsKey(functor))) {
			ASSyntax.LOGGER.log(Level.WARNING, "as2j warning: [" + getErrorMsg() + "] the term '" + functor + "' has an explicitly defined module prefix ('" + moduleName + "'), hence that has been used, in the source file '" + agent.getASLSrc() + "'");
		}
		checkForExportModule = module;
		functorWithoutModule = functor;
		functor = module.getPrefix() + functor;
		prettyModulePrefix = moduleName;
		moduleUsed = moduleName;
		modulePrefixApplied = true;
	}
	
	public void checkForExport(SourceInfo si, Token token, Agent ag) throws ParseException {
		if (checkForExportModule != null) {
			if(checkForExportModule.getName().isEmpty() && ag.isSideKick()) return;
			
			PredicateIndicator pi = new PredicateIndicator(functorWithoutModule, getArity());
			HashSet<PredicateIndicator> exports = checkForExportModule.getExports();
			if (!exports.contains(pi) && !exports.contains(new PredicateIndicator(PredicateIndicator.ANY_FUNCTOR, getArity())) && !exports.contains(new PredicateIndicator(functorWithoutModule, PredicateIndicator.ANY_ARITY)) && !exports.contains(PredicateIndicator.ANY)) {
				throw new ParseException('[' + (si == null ? "" : si.toString()) + "] The " + (checkForExportModule.getName().isEmpty() ? "agent" : "module") + " (" + checkForExportModule + ") has no such exported predicate: '" + pi + "'.", token);
			}
		}
	}
	
	public Term clone() {
		//return this; // the single muting occurs when the module prefixes are applied and then the original ones are garbage collected anyway, so just could use the same object, if it's really slow otherwise...
		return new Atom(this); // let's make it mutable! -> modularity
	}
	
	@Override
	public boolean isAtom() {
		return true;
	}
	
	@Override
	public boolean equals(Object o) {
		if (o == null) return false;
		if (o == this) return true;
		if (o instanceof Atom) {
			Atom a = (Atom)o;
			//System.out.println(getFunctor() +" ==== " + a.getFunctor() + " is "+ (a.isAtom())); // && getFunctor().equals(a.getFunctor())));
			return a.isAtom() && getFunctor().equals(a.getFunctor());
		}
		return false;
	}
	
	public int compareTo(Term t) {
		if (t.isNumeric()) return 1;
		
		// this is a list and the other not
		if (isList() && !t.isList()) return 1;
		
		// this is not a list and the other is
		if (!isList() && t.isList()) return -1;
		
		// both are lists, check the size
		if (isList() && t.isList()) {
			ListTerm l1 = (ListTerm)this;
			ListTerm l2 = (ListTerm)t;
			final int l1s = l1.size();
			final int l2s = l2.size();
			if (l1s > l2s) return 1;
			if (l2s > l1s) return -1;
			return 0; // need to check elements (in Structure class)
		}
		
		if (t instanceof Atom) {
			Atom tAsAtom = (Atom)t;
			return getFunctor().compareTo(tAsAtom.getFunctor());
		}
		
		return super.compareTo(t);
	}
	
	@Override
	protected int calcHashCode() {
		return getFunctor().hashCode();
	}
	
	public String getPrettyFunctor() {
		if (modulePrefixApplied && !functorWithoutModule.equals(functor)) { // functorWithoutModule == functor --> ::term
			return prettyModulePrefix + Agent.Module.OPERATOR + functorWithoutModule;// + "[@" + functor + "|" + this.getClass() + "]";
		}
		return functor;
	}
	
	@Override
	public String toString() {
		return getPrettyFunctor();
	}
	
	/** get as XML */
	public Element getAsDOM(Document document) {
		Element u = (Element)document.createElement("structure");
		u.setAttribute("functor", getPrettyFunctor());
		return u;
	}
}
